import axios, {
  type AxiosResponse,
  type AxiosRequestConfig,
  type AxiosInstance,
  type CancelTokenSource,
} from 'axios'

import { isBoolean, isString, isNumber } from 'lodash'

// 重新包装一下类型
export type CXReqestInstance = AxiosInstance
export type CXCancelTokenSource = CancelTokenSource
export type CXRequestConfig = AxiosRequestConfig
export type CXResponse<T> = AxiosResponse<T>

function isExactObject(data: any): boolean {
  return Object.prototype.toString.call(data) === '[object Object]'
}

class RequestClass {
  public isCancel: (error: Error) => boolean

  private _axios: CXReqestInstance

  public constructor(CXReqestInstance: CXReqestInstance) {
    this._axios = CXReqestInstance

    this.isCancel = axios.isCancel
  }

  /**
   * 创建一个axios实例
   * @returns {RequestClass} axios实例
   */
  public static createInstance(): RequestClass {
    const newAxios = axios.create({})
    return new RequestClass(newAxios)
  }

  /**
   * 通用请求接口
   * @param {CXRequestConfig} config 配置项
   * @returns {Promise<CXResponse<any>>} response
   * @memberof RequestClass
   */
  public request(config: CXRequestConfig): Promise<CXResponse<any>> {
    return this._axios(config)
  }

  /**
   * 发送get请求
   * @param  {string} url    url地址
   * @param {?Object} query 查询
   * @param  {Partial<CXRequestConfig>} [options={}] axios额外选项
   * @return {Promise<CXResponse<any>>}        promise对象
   */
  public get<T = any>(
    url: string,
    query?: Record<string, any>,
    options: Partial<CXRequestConfig> = {},
  ): Promise<CXResponse<T>> {
    let realUrl = url
    // 添加querystring
    if (query && isExactObject(query)) {
      const keys = Object.keys(query)
      const queryItems = []
      for (const key of keys) {
        const value = query[key]
        // 过滤value的类型
        if (isBoolean(value) || isString(value) || isNumber(value)) {
          const itemstring = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
          queryItems.push(itemstring)
        }
      }
      if (queryItems.length > 0) {
        const querystring = queryItems.join('&')
        realUrl = `${realUrl}?${querystring}`
      }
    }

    const promise: Promise<CXResponse<T>> = this._axios.get<T>(realUrl, {
      ...options,
    })

    return promise
  }

  /**
   * 发送post请求
   * @param  {string} url        url地址
   * @param  {any} [data={}]    数据
   * @param  {Partial<CXRequestConfig>} [options={}] 额外选项
   * @return {Promise<CXResponse<any>>}              [description]
   */
  public post<T = any>(
    url: string,
    data: any = {},
    options: Partial<CXRequestConfig> = {},
  ): Promise<CXResponse<T>> {
    const realUrl = url
    const promise: Promise<CXResponse<T>> = this._axios.post(realUrl, data, {
      ...options,
    })

    return promise
  }

  /**
   * 发送delete请求
   * @param  {string} url     [description]
   * @param  {CXRequestConfig} [options={}] [description]
   * @return {Promise<CXResponse<any>>}         [description]
   */
  public delete<T = any>(
    url: string,
    options: Partial<CXRequestConfig> = {},
  ): Promise<CXResponse<T>> {
    const realUrl = url

    const promise: Promise<CXResponse<T>> = this._axios.delete<T>(realUrl, {
      ...options,
    })

    return promise
  }

  /**
   * 发送put请求
   * @param  {string} url        [description]
   * @param  {any} data       [description]
   * @param  {CXRequestConfig} options    [description]
   * @return {Promise<CXResponse<any>>}            [description]
   */
  public put<T = any>(
    url: string,
    data: any = {},
    options: Partial<CXRequestConfig> = {},
  ): Promise<CXResponse<T>> {
    const realUrl = url

    const promise: Promise<CXResponse<T>> = this._axios.put<T>(realUrl, data, {
      ...options,
    })

    return promise
  }

  /**
   * 给request添加拦截器
   * @NOTE: 所添加的拦截器按照添加的顺序反向进行调用，底层也是会将这些返回值封装Promise，从而形成链式调用的形式
   * @param {Function} fullfil (Object) => Object, 接收一个配置，返回另一个配置
   * @param {Function} reject  [description]
   * @return {number}  handle， 用来用取消
   */
  public addRequestInterceptor(
    fullfil?: (value: CXRequestConfig) => CXRequestConfig | Promise<CXRequestConfig> | any,
    reject?: (err: any) => any,
  ): number {
    return this._axios.interceptors.request.use(fullfil, reject)
  }

  /**
   * 移除handle对应的request拦截器
   * @param  {number} handle 可通过addRequestInterceptor返回值获得
   * @return {void}        [description]
   */
  public removeRequestInterceptor(handle: number): void {
    this._axios.interceptors.request.eject(handle)
  }

  /**
   * 添加responese拦截器
   * @NOTE: 所添加的拦截器按照添加的顺序进行调用，底层也是会将这些返回值封装Promise，从而形成链式调用的形式
   * @param {[type]} fullfil [description]
   * @param {Promise<Error>} reject  [description]
   * @return {number} handle， 用来用取消
   */
  public addResponeseInterceptor(
    fullfil: (obj: CXResponse<any>) => CXResponse<any> | Promise<CXResponse<any>>,
    reject?: (err: any) => any,
  ): number {
    return this._axios.interceptors.response.use(fullfil, reject)
  }

  /**
   * 移除handle对应的responese拦截器
   * @param  {number} handle 可通过addResponeseInterceptor返回值获得
   * @return {void}         [description]
   */
  public removeResponeseInterceptor(handle: number): void {
    this._axios.interceptors.response.eject(handle)
  }

  /**
   * 获得可取消的handle
   * @returns {CXCancelTokenSource} CXCancelTokenSource
   * @memberof RequestClass
   */
  public getCancelSource(): CXCancelTokenSource {
    const { CancelToken } = axios
    return CancelToken.source()
  }
}

const defaultAxios = axios.create({})
const GJXRequest = new RequestClass(defaultAxios)

export { GJXRequest, RequestClass }
